# Kestrel: An XMPP-based Many-Task Computing Scheduler
# Author: Lance Stout <lancestout@gmail.com>
#
# Copyright 2009-2010 Clemson University
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""
XMPP client module.

The XMPP module provides an interface for sending and receiving
both XMPP messages and presence notifications.
"""

import yaml


class Base(object):
    """
    Collection of XMPP event handlers common to both the Client and Component
    implementations.
    """

    def __init__(self):
        """
        Instantiate a group of basic XMPP connection handlers.
        """
        self.xmpp = None
        self.kernel = None
        self.has_roster = True
        self.jid = ''

    def disconnect(self, data=None):
        """Disconnect from the XMPP server."""
        self.xmpp.disconnect()
        self.kernel.event('system/offline')

    def on_start(self, event):
        """Fire system/available event once a connection has been made."""
        if self.has_roster:
            self.xmpp.getRoster()
        self.xmpp.sendPresence()
        self.kernel.event('system/online')
        self.kernel.event('system/available')

    def recv_status(self, presence):
        """Fire a presence event when a presence notification is received."""
        jid = str(presence['from'])
        bare = presence['from'].bare

        presence_map = {'chat': 'presence/available',
                        'available': 'presence/available',
                        'away': 'presence/busy',
                        'dnd': 'presence/busy',
                        'unavailable': 'presence/offline'}
        event_type = presence_map.get(presence['type'], 'unavailable')

        self.kernel.event(event_type, {'full': jid, 'bare': bare})

    def recv_message(self, msg):
        """
        Fire a message event when a message has been received.

        Currently, it is assumed that message bodies will be
        written in either YAML or JSON.
        """
        sender = str(msg['from'])
        packet = yaml.load(msg['body'])

        if msg['type'] == 'error':
            self.kernel.event('message/error', {'from': sender,
                                                'body': packet})
        else:
            self.kernel.event('message', {'from': sender, 'body': packet})

    def send_status(self, status):
        """
        Send presence notification that the system is either
        available or busy.

        Status can either be a pure broadcast, or directed
        to particular entity. For a directed presence, use
        the format {'to': 'user@example.com', 'type':'available'}.
        """

        to = None
        if type(status) is dict:
            to = status['to']
            status = status['type']

        presence_map = {'available': {'pshow': 'chat',
                                      'pstatus': 'Available'},
                        'busy': {'pshow': 'dnd',
                                 'pstatus': 'Running Task'},
                        'subscribe': {'ptype': 'subscribe'},
                        'unsubscribe': {'ptype': 'unsubscribe'},
                        'subscribed': {'ptype': 'subscribed'},
                        'unsubscribed': {'ptype': 'unsubscribed'}}

        presence = presence_map.get(status, None)
        if None not in [presence, to]:
            presence['pto'] = to

        state = self.kernel.config('system/status', 'available')
        state = presence_map[state]['pshow']

        if status == 'subscribe' \
                and to in self.xmpp.roster.keys() \
                and self.xmpp.roster[to]['subscription'] == 'both':
            self.kernel.event('system/access/approved', to)
            self.xmpp.sendPresence(pto=to, pshow=state)
            return

        if None not in [self.xmpp, presence]:
            if status in ['available', 'busy']:
                for jid in self.xmpp.roster.keys():
                    presence['pto'] = jid
                    self.xmpp.sendPresence(**presence)
            elif status in ['subscribe', 'unsubscribe',
                            'unsubscribe', 'unsubscribed']:
                self.xmpp.sendPresence(**presence)
                self.xmpp.sendPresence(pto=to, pshow=state)
            else:
                self.xmpp.sendPresence(**presence)

    def send_message(self, packet):
        """
        Send an XMPP message. There are no assumptions made about
        the content of the message.
        """
        if self.xmpp is not None:
            self.xmpp.sendMessage(mto=packet['to'],
                                  mbody=packet['body'],
                                  mfrom=self.jid)

    def change_roster(self, presence):
        """Fire a subscribe or unsubscribe event when a request is received."""
        jid = presence['from'].bare

        presence_map = {'subscribe': 'presence/subscribe',
                        'unsubscribe': 'presence/unsubsribe',
                        'subscribed': 'presence/subscribed',
                        'unsubscribed': 'presence/unsubscribed'}
        event_type = presence_map.get(presence['type'])

        self.kernel.event(event_type, jid)
